msg_tool\scripts\escude/
list.rs

1//! Escu:de List File (.bin)
2use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use crate::utils::encoding::{decode_to_string, encode_string};
6use crate::utils::struct_pack::*;
7use anyhow::Result;
8use msg_tool_macro::*;
9use serde::{Deserialize, Serialize};
10use std::io::{Read, Seek, Write};
11
12#[derive(Debug)]
13/// Escu:de List Builder
14pub struct EscudeBinListBuilder {}
15
16impl EscudeBinListBuilder {
17    /// Creates a new instance of `EscudeBinListBuilder`
18    pub const fn new() -> Self {
19        EscudeBinListBuilder {}
20    }
21}
22
23impl ScriptBuilder for EscudeBinListBuilder {
24    fn default_encoding(&self) -> Encoding {
25        Encoding::Cp932
26    }
27
28    fn build_script(
29        &self,
30        data: Vec<u8>,
31        filename: &str,
32        encoding: Encoding,
33        _archive_encoding: Encoding,
34        config: &ExtraConfig,
35        _archive: Option<&Box<dyn Script>>,
36    ) -> Result<Box<dyn Script>> {
37        Ok(Box::new(EscudeBinList::new(
38            data, filename, encoding, config,
39        )?))
40    }
41
42    fn extensions(&self) -> &'static [&'static str] {
43        &["bin"]
44    }
45
46    fn script_type(&self) -> &'static ScriptType {
47        &ScriptType::EscudeList
48    }
49
50    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
51        if buf_len > 4 && buf.starts_with(b"LIST") {
52            return Some(255);
53        }
54        None
55    }
56
57    fn can_create_file(&self) -> bool {
58        true
59    }
60
61    fn create_file<'a>(
62        &'a self,
63        filename: &'a str,
64        writer: Box<dyn WriteSeek + 'a>,
65        encoding: Encoding,
66        file_encoding: Encoding,
67        config: &ExtraConfig,
68    ) -> Result<()> {
69        create_file(
70            filename,
71            writer,
72            encoding,
73            file_encoding,
74            config.custom_yaml,
75        )
76    }
77}
78
79#[derive(Debug)]
80/// Escu:de Binary List
81pub struct EscudeBinList {
82    /// List of entries in the Escu:de list
83    pub entries: Vec<ListEntry>,
84    custom_yaml: bool,
85}
86
87impl EscudeBinList {
88    /// Creates a new `EscudeBinList` from the given data
89    ///
90    /// * `data` - The reader to read the data from
91    /// * `filename` - The name of the file
92    /// * `encoding` - The encoding
93    /// * `config` - Extra configuration options
94    pub fn new(
95        data: Vec<u8>,
96        filename: &str,
97        encoding: Encoding,
98        config: &ExtraConfig,
99    ) -> Result<Self> {
100        let mut reader = MemReader::new(data);
101        let mut magic = [0; 4];
102        reader.read_exact(&mut magic)?;
103        if &magic != b"LIST" {
104            return Err(anyhow::anyhow!("Invalid Escude list file format"));
105        }
106        let wsize = reader.read_u32()?;
107        let mut entries = Vec::new();
108        loop {
109            let current = reader.stream_position()?;
110            if current as usize >= wsize as usize + 8 {
111                break;
112            }
113            let id = reader.read_u32()?;
114            let size = reader.read_u32()?;
115            let data = reader.read_exact_vec(size as usize)?;
116            entries.push(ListEntry {
117                id: id,
118                data: ListData::Unknown(data),
119            });
120        }
121        let mut s = EscudeBinList {
122            entries,
123            custom_yaml: config.custom_yaml,
124        };
125        match s.try_decode(filename, encoding) {
126            Ok(_) => {}
127            Err(e) => {
128                eprintln!("WARN: Failed to decode Escude list: {}", e);
129                crate::COUNTER.inc_warning();
130            }
131        }
132        Ok(s)
133    }
134
135    /// Attempts to decode the entries in the list based on the filename and encoding
136    ///
137    /// * `filename` - The name of the file
138    /// * `encoding` - The encoding to use for decoding
139    pub fn try_decode(&mut self, filename: &str, encoding: Encoding) -> Result<()> {
140        let filename = std::path::Path::new(filename);
141        if let Some(filename) = filename.file_name() {
142            let filename = filename.to_ascii_lowercase();
143            if filename == "enum_scr.bin" {
144                for ent in self.entries.iter_mut() {
145                    let id = ent.id;
146                    if let ListData::Unknown(unk) = &ent.data {
147                        let mut reader = MemReader::new(unk.clone());
148                        let mut element_size = if id == 0 {
149                            132
150                        } else if id == 1 {
151                            100
152                        } else if id == 2 {
153                            36
154                        } else if id == 3 {
155                            104
156                        } else if id == 9999 {
157                            1
158                        } else {
159                            return Err(anyhow::anyhow!("Unknown enum source ID: {}", id));
160                        };
161                        let len = unk.len();
162                        let mut script_old = false;
163                        if id == 0 && len % element_size != 0 {
164                            element_size = 128;
165                            script_old = true;
166                        }
167                        if len % element_size != 0 {
168                            return Err(anyhow::anyhow!(
169                                "Invalid enum source length: {} for ID: {}",
170                                len,
171                                id
172                            ));
173                        }
174                        let count = len / element_size;
175                        let data_entry = match id {
176                            0 => {
177                                if script_old {
178                                    ListData::Scr(EnumScr::Scripts2(
179                                        reader.read_struct_vec::<ScriptT2>(
180                                            count, false, encoding, &None,
181                                        )?,
182                                    ))
183                                } else {
184                                    ListData::Scr(EnumScr::Scripts(
185                                        reader.read_struct_vec::<ScriptT>(
186                                            count, false, encoding, &None,
187                                        )?,
188                                    ))
189                                }
190                            }
191                            1 => ListData::Scr(EnumScr::Names(
192                                reader.read_struct_vec::<NameT>(count, false, encoding, &None)?,
193                            )),
194                            2 => ListData::Scr(EnumScr::Vars(
195                                reader.read_struct_vec::<VarT>(count, false, encoding, &None)?,
196                            )),
197                            3 => ListData::Scr(EnumScr::Scenes(
198                                reader.read_struct_vec::<SceneT>(count, false, encoding, &None)?,
199                            )),
200                            9999 => {
201                                // Special case for unknown enum source ID
202                                ListData::Unknown(unk.clone())
203                            }
204                            _ => return Err(anyhow::anyhow!("Unknown enum source ID: {}", id)),
205                        };
206                        ent.data = data_entry;
207                    }
208                }
209            } else if filename == "enum_gfx.bin" {
210                for ent in self.entries.iter_mut() {
211                    let id = ent.id;
212                    if let ListData::Unknown(unk) = &ent.data {
213                        let mut reader = MemReader::new(unk.clone());
214                        let element_size = if id == 0 {
215                            248
216                        } else if id == 1 {
217                            248
218                        } else if id == 2 {
219                            248
220                        } else if id == 3 {
221                            112
222                        } else if id == 4 {
223                            32
224                        } else if id == 9999 {
225                            1
226                        } else {
227                            return Err(anyhow::anyhow!("Unknown enum gfx ID: {}", id));
228                        };
229                        let len = unk.len();
230                        if len % element_size != 0 {
231                            return Err(anyhow::anyhow!(
232                                "Invalid enum gfx length: {} for ID: {}",
233                                len,
234                                id
235                            ));
236                        }
237                        let count = len / element_size;
238                        let data_entry = match id {
239                            0 => ListData::Gfx(EnumGfx::Bgs(
240                                reader.read_struct_vec::<BgT>(count, false, encoding, &None)?,
241                            )),
242                            1 => ListData::Gfx(EnumGfx::Evs(
243                                reader.read_struct_vec::<EvT>(count, false, encoding, &None)?,
244                            )),
245                            2 => ListData::Gfx(EnumGfx::Sts(
246                                reader.read_struct_vec::<StT>(count, false, encoding, &None)?,
247                            )),
248                            3 => ListData::Gfx(EnumGfx::Efxs(
249                                reader.read_struct_vec::<EfxT>(count, false, encoding, &None)?,
250                            )),
251                            4 => ListData::Gfx(EnumGfx::Locs(
252                                reader.read_struct_vec::<LocT>(count, false, encoding, &None)?,
253                            )),
254                            9999 => {
255                                // Special case for unknown enum gfx ID
256                                ListData::Unknown(unk.clone())
257                            }
258                            _ => return Err(anyhow::anyhow!("Unknown enum gfx ID: {}", id)),
259                        };
260                        ent.data = data_entry;
261                    }
262                }
263            } else if filename == "enum_snd.bin" {
264                for ent in self.entries.iter_mut() {
265                    let id = ent.id;
266                    if let ListData::Unknown(unk) = &ent.data {
267                        let mut reader = MemReader::new(unk.clone());
268                        let element_size = if id == 0 {
269                            196
270                        } else if id == 1 {
271                            128
272                        } else if id == 2 {
273                            128
274                        } else if id == 3 {
275                            128
276                        } else if id == 9999 {
277                            1
278                        } else {
279                            return Err(anyhow::anyhow!("Unknown enum sound ID: {}", id));
280                        };
281                        let len = unk.len();
282                        if len % element_size != 0 {
283                            return Err(anyhow::anyhow!(
284                                "Invalid enum sound length: {} for ID: {}",
285                                len,
286                                id
287                            ));
288                        }
289                        let count = len / element_size;
290                        let data_entry = match id {
291                            0 => ListData::Snd(EnumSnd::Bgm(
292                                reader.read_struct_vec::<BgmT>(count, false, encoding, &None)?,
293                            )),
294                            1 => ListData::Snd(EnumSnd::Amb(
295                                reader.read_struct_vec::<AmbT>(count, false, encoding, &None)?,
296                            )),
297                            2 => ListData::Snd(EnumSnd::Se(
298                                reader.read_struct_vec::<SeT>(count, false, encoding, &None)?,
299                            )),
300                            3 => ListData::Snd(EnumSnd::Sfx(
301                                reader.read_struct_vec::<SfxT>(count, false, encoding, &None)?,
302                            )),
303                            9999 => {
304                                // Special case for unknown enum sound ID
305                                ListData::Unknown(unk.clone())
306                            }
307                            _ => return Err(anyhow::anyhow!("Unknown enum sound ID: {}", id)),
308                        };
309                        ent.data = data_entry;
310                    }
311                }
312            }
313        }
314        Ok(())
315    }
316}
317
318fn create_file<'a>(
319    custom_filename: &'a str,
320    mut writer: Box<dyn WriteSeek + 'a>,
321    encoding: Encoding,
322    output_encoding: Encoding,
323    yaml: bool,
324) -> Result<()> {
325    let input = crate::utils::files::read_file(custom_filename)?;
326    let s = decode_to_string(output_encoding, &input, true)?;
327    let entries: Vec<ListEntry> = if yaml {
328        serde_yaml_ng::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse YAML: {}", e))?
329    } else {
330        serde_json::from_str(&s).map_err(|e| anyhow::anyhow!("Failed to parse JSON: {}", e))?
331    };
332    writer.write_all(b"LIST")?;
333    writer.write_u32(0)?; // Placeholder for size
334    let mut total_size = 0;
335    for entry in entries {
336        let cur_pos = writer.stream_position()?;
337        writer.write_u32(entry.id)?;
338        writer.write_u32(0)?; // Placeholder for size
339        entry.data.pack(&mut writer, false, encoding, &None)?;
340        let end_pos = writer.stream_position()?;
341        let size = (end_pos - cur_pos - 8) as u32; // 8 bytes for id and size
342        writer.seek(std::io::SeekFrom::Start(cur_pos + 4))?; // Seek to size position
343        writer.write_u32(size)?;
344        writer.seek(std::io::SeekFrom::Start(end_pos))?; // Seek to end
345        total_size += size + 8;
346    }
347    writer.seek(std::io::SeekFrom::Start(4))?; // Seek back to size position
348    writer.write_u32(total_size)?;
349    writer.flush()?;
350    Ok(())
351}
352
353impl Script for EscudeBinList {
354    fn default_output_script_type(&self) -> OutputScriptType {
355        OutputScriptType::Custom
356    }
357
358    fn is_output_supported(&self, output: OutputScriptType) -> bool {
359        matches!(output, OutputScriptType::Custom)
360    }
361
362    fn default_format_type(&self) -> FormatOptions {
363        FormatOptions::None
364    }
365
366    fn custom_output_extension(&self) -> &'static str {
367        if self.custom_yaml { "yaml" } else { "json" }
368    }
369
370    fn custom_export(&self, filename: &std::path::Path, encoding: Encoding) -> Result<()> {
371        let s = if self.custom_yaml {
372            serde_yaml_ng::to_string(&self.entries)
373                .map_err(|e| anyhow::anyhow!("Failed to serialize to YAML: {}", e))?
374        } else {
375            serde_json::to_string_pretty(&self.entries)
376                .map_err(|e| anyhow::anyhow!("Failed to serialize to JSON: {}", e))?
377        };
378        let mut writer = crate::utils::files::write_file(filename)?;
379        let s = encode_string(encoding, &s, false)?;
380        writer.write_all(&s)?;
381        writer.flush()?;
382        Ok(())
383    }
384
385    fn custom_import<'a>(
386        &'a self,
387        custom_filename: &'a str,
388        writer: Box<dyn WriteSeek + 'a>,
389        encoding: Encoding,
390        output_encoding: Encoding,
391    ) -> Result<()> {
392        create_file(
393            custom_filename,
394            writer,
395            encoding,
396            output_encoding,
397            self.custom_yaml,
398        )
399    }
400}
401
402#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
403/// Script entry in the Escu:de list
404pub struct ScriptT {
405    #[fstring = 64]
406    #[fstring_pad = 0x20]
407    /// File name
408    pub file: String,
409    /// Script ID
410    pub source: u32,
411    #[fstring = 64]
412    #[fstring_pad = 0x20]
413    /// Script title
414    pub title: String,
415}
416
417#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
418/// Script entry in the Escu:de list
419pub struct ScriptT2 {
420    #[fstring = 64]
421    #[fstring_pad = 0x20]
422    /// File name
423    pub file: String,
424    #[fstring = 64]
425    #[fstring_pad = 0x20]
426    /// Script title
427    pub title: String,
428}
429
430#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
431/// Name entry in the Escu:de list
432pub struct NameT {
433    #[fstring = 64]
434    #[fstring_pad = 0x20]
435    /// Name of the character
436    pub text: String,
437    /// Text color
438    pub color: u32,
439    #[fstring = 32]
440    #[fstring_pad = 0x20]
441    /// Face image file name
442    pub face: String,
443}
444
445#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
446/// Variable entry in the Escu:de list
447pub struct VarT {
448    /// Variable name
449    #[fstring = 32]
450    #[fstring_pad = 0x20]
451    pub name: String,
452    /// Variable value
453    pub value: u16,
454    /// Variable flag
455    pub flag: u16,
456}
457
458#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
459/// Scene entry in the Escu:de list
460pub struct SceneT {
461    /// The scene script ID
462    pub script: u32,
463    /// The scene name
464    #[fstring = 64]
465    #[fstring_pad = 0x20]
466    pub name: String,
467    /// The scene thumbail image file name
468    #[fstring = 32]
469    #[fstring_pad = 0x20]
470    pub thumbnail: String,
471    /// The scene order in the scene (Extra)
472    pub order: i32,
473}
474
475#[derive(Debug, Serialize, Deserialize, StructPack)]
476#[serde(tag = "type", content = "data")]
477/// Enum for different types of script data in the Escu:de list (enum._scr.bin)
478pub enum EnumScr {
479    /// Scripts data
480    Scripts(Vec<ScriptT>),
481    /// Scripts data (old)
482    Scripts2(Vec<ScriptT2>),
483    /// Names data
484    Names(Vec<NameT>),
485    /// Variables data
486    Vars(Vec<VarT>),
487    /// Scenes data
488    Scenes(Vec<SceneT>),
489}
490
491#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
492/// Background entry in the Escu:de list
493pub struct BgT {
494    /// Background image name
495    #[fstring = 32]
496    #[fstring_pad = 0x20]
497    name: String,
498    /// Background image file name
499    #[fstring = 64]
500    #[fstring_pad = 0x20]
501    file: String,
502    #[fstring = 128]
503    #[fstring_pad = 0x20]
504    /// Background options
505    option: String,
506    /// Background covered flag
507    coverd: u32,
508    /// Background color
509    color: u32,
510    /// Background ID
511    id: u32,
512    /// Background location ID
513    loc: u32,
514    /// Background order in the scene
515    order: i32,
516    /// Background link ID
517    link: u32,
518}
519
520#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
521/// Event image entry in the Escu:de list
522pub struct EvT {
523    /// Event image name
524    #[fstring = 32]
525    #[fstring_pad = 0x20]
526    name: String,
527    /// Event image file name
528    #[fstring = 64]
529    #[fstring_pad = 0x20]
530    file: String,
531    #[fstring = 128]
532    #[fstring_pad = 0x20]
533    /// Event image options
534    option: String,
535    /// Event image covered flag
536    coverd: u32,
537    /// Event image color
538    color: u32,
539    /// Event image ID
540    id: u32,
541    /// Event image location ID
542    loc: u32,
543    /// Event image order in the scene
544    order: i32,
545    /// Event image link ID
546    link: u32,
547}
548
549#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
550/// Stage entry in the Escu:de list
551pub struct StT {
552    #[fstring = 32]
553    #[fstring_pad = 0x20]
554    /// Stage name
555    name: String,
556    #[fstring = 64]
557    #[fstring_pad = 0x20]
558    /// Stage file name
559    file: String,
560    #[fstring = 128]
561    #[fstring_pad = 0x20]
562    /// Stage options
563    option: String,
564    /// Stage covered flag
565    coverd: u32,
566    /// Stage color
567    color: u32,
568    /// Stage ID
569    id: u32,
570    /// Stage location ID
571    loc: u32,
572    /// Stage order in the scene
573    order: i32,
574    /// Stage link ID
575    link: u32,
576}
577
578#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
579/// Effect image entry in the Escu:de list
580pub struct EfxT {
581    /// Effect image name
582    #[fstring = 32]
583    #[fstring_pad = 0x20]
584    name: String,
585    /// Effect image file name
586    #[fstring = 64]
587    #[fstring_pad = 0x20]
588    file: String,
589    /// Effect image options
590    spot: i32,
591    /// Effect image x position
592    dx: i32,
593    /// Effect image y position
594    dy: i32,
595    /// Effect image loop flag
596    r#loop: bool,
597    #[fvec = 3]
598    #[serde(skip, default = "exft_padding")]
599    /// padding for alignment
600    padding: Vec<u8>,
601}
602
603fn exft_padding() -> Vec<u8> {
604    vec![0; 3]
605}
606
607#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
608/// Point
609pub struct Point {
610    /// X coordinate
611    x: i16,
612    /// Y coordinate
613    y: i16,
614}
615
616#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
617/// Location entry in the Escu:de list
618pub struct LocT {
619    /// List of points
620    #[fvec = 8]
621    pt: Vec<Point>,
622}
623
624#[derive(Debug, Serialize, Deserialize, StructPack)]
625#[serde(tag = "type", content = "data")]
626/// Enum for different types of graphics data in the Escu:de list (enum_gfx.bin)
627pub enum EnumGfx {
628    /// Backgrounds data
629    Bgs(Vec<BgT>),
630    /// Event images data
631    Evs(Vec<EvT>),
632    /// Stages data
633    Sts(Vec<StT>),
634    /// Effects data
635    Efxs(Vec<EfxT>),
636    /// Locations data
637    Locs(Vec<LocT>),
638}
639
640#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
641/// Background music entry in the Escu:de list
642pub struct BgmT {
643    #[fstring = 64]
644    #[fstring_pad = 0x20]
645    /// Background music name
646    pub name: String,
647    #[fstring = 64]
648    #[fstring_pad = 0x20]
649    /// Background music file name
650    pub file: String,
651    #[fstring = 64]
652    #[fstring_pad = 0x20]
653    /// Background music title
654    pub title: String,
655    /// Background music order in the scene
656    pub order: i32,
657}
658
659#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
660/// Ambient sound entry in the Escu:de list
661pub struct AmbT {
662    #[fstring = 64]
663    #[fstring_pad = 0x20]
664    /// Ambient sound name
665    pub name: String,
666    #[fstring = 64]
667    #[fstring_pad = 0x20]
668    /// Ambient sound file name
669    pub file: String,
670}
671
672#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
673/// Sound effect entry in the Escu:de list
674pub struct SeT {
675    #[fstring = 64]
676    #[fstring_pad = 0x20]
677    /// Sound effect name
678    pub name: String,
679    #[fstring = 64]
680    #[fstring_pad = 0x20]
681    /// Sound effect file name
682    pub file: String,
683}
684
685#[derive(Debug, Serialize, Deserialize, StructPack, StructUnpack)]
686/// Sound effect (SFX) entry in the Escu:de list
687pub struct SfxT {
688    #[fstring = 64]
689    #[fstring_pad = 0x20]
690    /// SFX name
691    pub name: String,
692    #[fstring = 64]
693    #[fstring_pad = 0x20]
694    /// SFX file name
695    pub file: String,
696}
697
698#[derive(Debug, Serialize, Deserialize, StructPack)]
699#[serde(tag = "type", content = "data")]
700/// Enum for different types of sound data in the Escu:de list (enum_snd.bin)
701pub enum EnumSnd {
702    /// Background music data
703    Bgm(Vec<BgmT>),
704    /// Ambient sound data
705    Amb(Vec<AmbT>),
706    /// Sound effect data
707    Se(Vec<SeT>),
708    /// Sound effect (SFX) data
709    Sfx(Vec<SfxT>),
710}
711
712#[derive(Debug, Serialize, Deserialize, StructPack)]
713#[serde(tag = "type", content = "data")]
714/// Enum for different types of data in the Escu:de list
715pub enum ListData {
716    /// Script data
717    Scr(EnumScr),
718    /// Graphics data
719    Gfx(EnumGfx),
720    /// Sound data
721    Snd(EnumSnd),
722    /// Unknown data
723    Unknown(Vec<u8>),
724}
725
726#[derive(Debug, Serialize, Deserialize)]
727/// Entry in the Escu:de list
728pub struct ListEntry {
729    id: u32,
730    /// The data associated with the entry
731    pub data: ListData,
732}